# -*- coding: utf-8 -*-
# @Time    : 2025/10/5
# @Author  : 楚江涛
# @FileName: interfaces.py
# @Software: PyCharm


"""
app/execution/interfaces.py
---------------------------------
本文件定义“执行层”的可插拔接口（Protocols）与通用类型。

目标：
1) 让 WorkflowExecutor 仅依赖接口，便于替换基础设施实现（内存 → Redis/DB；内联执行 → 子进程/容器）。
2) 统一事件、状态、入参/出参的类型约束，提升可读性与可维护性。
3) 为高级实现预留扩展点（如批量发布、订阅、关闭、超时、环境变量等）。
"""

from __future__ import annotations

from typing import (
    Protocol,
    runtime_checkable,
    Dict,
    Any,
    Optional,
    Mapping,
    MutableMapping,
    Sequence,
    Tuple,
    Union,
)

from domain.enums import EventType, ExecStatus


# ============================================================
# 一、通用类型别名（避免魔法字典散落）
# ============================================================

# 节点参数：聚合前驱输出 + 全局上下文 + 节点默认入参
CodeParams = Dict[str, Any]

# 节点返回：必须为字典；建议包含 "result"/"metrics"/"artifacts" 等键
NodeResult = Dict[str, Any]

# 代码节点的源码字符串
CodeText = str

# 运行时可选参数（代码执行器可能用到）
RunnerEnv = Mapping[str, str]  # 环境变量（只读映射）
RunnerMeta = Mapping[str, Any]  # 执行元数据（llm 温度/提示模板/镜像标签等）

# 事件负载：统一为 dict，执行器会注入 exec_id
EventPayload = Dict[str, Any]


# ============================================================
# 二、事件发布接口
# ============================================================

@runtime_checkable
class EventPublisher(Protocol):
    """
    事件发布器：负责将执行过程中的事件投递到外部（SSE/消息总线/日志系统）。

    必选方法：
      - publish(exec_id, event, payload)

    可选能力（实现类可提供，不强制）：
      - publish_many(items)            # 批量发布以减少开销
      - close()                        # 释放资源
    """

    def publish(self, exec_id: str, event: EventType, payload: EventPayload) -> None:
        """发布单条事件（要求：快速、非阻塞或尽可能少阻塞）"""
        ...

    # --- 可选扩展：若实现不支持，直接不实现即可 ---
    def publish_many(self, records: Sequence[Tuple[str, EventType, EventPayload]]) -> None:  # pragma: no cover
        """批量发布事件（可选）"""
        raise NotImplementedError

    def close(self) -> None:  # pragma: no cover
        """释放底层连接/句柄（可选）"""
        raise NotImplementedError


# ============================================================
# 三、状态存储接口
# ============================================================

@runtime_checkable
class StateStore(Protocol):
    """
    状态存储：维护执行/节点的“可查询快照”，供 /status 接口返回。

    约定：
      - 所有更新方法应当“幂等”；
      - 所有写操作尽量轻量且线程/协程安全；
      - snapshot() 返回简洁数据结构，避免过大（日志和大对象请外置存储）。
    """

    # ---------- 执行级 ----------
    def init(self, exec_id: str) -> None:
        """初始化执行快照（PENDING 状态；清空运行中/已完成/错误集）"""
        ...

    def update_status(self, exec_id: str, status: ExecStatus) -> None:
        """更新执行状态（RUNNING/SUCCEEDED/FAILED/CANCELLED）"""
        ...

    def set_progress(self, exec_id: str, progress: float) -> None:  # 可选
        """设置进度（0.0 ~ 1.0），实现类可忽略"""
        ...

    # ---------- 节点级 ----------
    def mark_node_running(self, exec_id: str, node_id: str) -> None:
        """将节点标记为运行中（若已在列表则忽略）"""
        ...

    def mark_node_finished(self, exec_id: str, node_id: str) -> None:
        """将节点标记为完成：从 running 移除并加入 finished"""
        ...

    def mark_node_failed(self, exec_id: str, node_id: str, error: str) -> None:
        """将节点记为失败并附带错误信息；通常同时推动执行状态为 FAILED"""
        ...

    # ---------- 读取 ----------
    def snapshot(self, exec_id: str) -> Dict[str, Any]:
        """
        返回执行快照，建议结构：
        {
          "status": "RUNNING",
          "running": ["nodeA", "nodeB"],
          "finished": ["start_0"],
          "errors": {"nodeX": "错误文本"},
          "progress": 0.42   # 可选
        }
        """
        ...


# ============================================================
# 四、代码执行器接口
# ============================================================

@runtime_checkable
class CodeRunner(Protocol):
    """
    代码执行器：执行“代码节点”的源码，并返回结构化结果。

    语义要求：
      - run() 必须为可等待的异步方法（async），以便与 asyncio 协作；
      - 取消：当外层 Task 被取消时，应尽快响应（协作式取消）；
      - 成功：返回 NodeResult（dict）；
      - 失败：抛出异常（建议使用领域异常 CodeRunnerError/InvalidCodeNode）；
      - 超时：实现内部可选择支持 timeout；若不支持，由调用方用 wait_for 包裹。

    参数说明：
      - node_id:    节点标识（用于日志与可观测）
      - code:       源码字符串（例如 `async def main(args): ...`）
      - params:     运行参数（聚合后的 inputs）
      - timeout:    可选，秒；实现可忽略，由调用方控制
      - env:        可选，环境变量（子进程/容器常用）
      - metadata:   可选，执行元信息（镜像标签、资源限额、LLM 超参等）
    """

    async def run(
        self,
        node_id: str,
        code: CodeText,
        params: CodeParams,
        *,
        timeout: Optional[float] = None,
        env: Optional[RunnerEnv] = None,
        metadata: Optional[RunnerMeta] = None,
    ) -> NodeResult:
        """执行代码节点，返回结构化结果（dict）"""
        ...


# ============================================================
# 五、可选：订阅能力（供 SSE 网关或消息中间件使用）
# ============================================================

@runtime_checkable
class SubscribableEventBus(EventPublisher, Protocol):  # 可选扩展
    """
    具备订阅能力的事件总线（SSE/长轮询可能需要）。
    仅在基础设施层使用；执行器层不依赖该接口。

    语义要求：
      - subscribe() 需返回可被 put_nowait/get 的队列对象（或将队列注册到内部映射）。
      - unsubscribe() 解除订阅。
    """

    def subscribe(self, exec_id: str, queue_like: Any) -> None:
        """注册一个订阅队列（例如 asyncio.Queue）以接收该 exec 的事件"""
        ...

    def unsubscribe(self, exec_id: str, queue_like: Any) -> None:
        """解除订阅"""
        ...

